#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <stdint.h>
#include "../../List.h"
#include "ParallelCompiler.h"
#include "../BMC.h"
#include "Translation.h"

ParallelCompilationQueue* CQ;
ParallelCompiler* PCs;
uint32_t NumParallelCompilers;

void CQ_Init(void){
    CQ = malloc(sizeof(ParallelCompilationQueue));
    pthread_mutex_init(&(CQ->M), NULL);
    CQ->OrderList = LST_New(NULL);
}

static void* CQ_Checkout(void){
    void* Order;
    
    pthread_mutex_lock(&(CQ->M));
    Order = LST_Dequeue(CQ->OrderList);
    pthread_mutex_unlock(&(CQ->M));
    
    return Order;
}

static void CQ_Enqueue(BMCList* CompilationOrder){
    pthread_mutex_lock(&(CQ->M));
    LST_Enqueue(CQ->OrderList, CompilationOrder);
    pthread_mutex_unlock(&(CQ->M));
}

static void* PC_Loop(void* Args){
    uint32_t MyID;
    BMCList* CompilationOrder;
    
    MyID = *((uint32_t*) Args);
    
    PCs[MyID].CompilerState = PCLS_Sleeping;
    while(1){
        switch(PCs[MyID].CompilerState){
            case PCLS_Sleeping:
                pthread_mutex_lock(&(PCs[MyID].M));
                        pthread_cond_wait(&(PCs[MyID].Cond), &(PCs[MyID].M));
                        pthread_cond_init(&(PCs[MyID].Cond), NULL);
                        
                        PCs[MyID].CompilerState = PCLS_Compiling;
                pthread_mutex_unlock(&(PCs[MyID].M));
            break;

            case PCLS_Compiling:
                CompilationOrder = CQ_Checkout();
                if(CompilationOrder){
                    TRL_TranslationRequest(CompilationOrder);
                }
                else
                    PCs[MyID].CompilerState = PCLS_Sleeping;
            break;
        }
    }
}

void PC_Init(uint32_t NumCompilers){
    uint32_t i;
    uint32_t *CompilerID;
    
    PCs = calloc(sizeof(ParallelCompiler) * (NumCompilers + 1), 1);
    
    NumParallelCompilers = NumCompilers;
    
    for(i = 0; i < NumCompilers; i++){
        CompilerID = malloc(sizeof(uint32_t));
        *CompilerID = i;
        pthread_cond_init(&(PCs[i].Cond), NULL);
        pthread_mutex_init(&(PCs[i].M), NULL);
        pthread_create(&(PCs[i].CompilerThreadID), NULL, PC_Loop, CompilerID);
    }
}

static uint32_t PC_Awake(uint32_t CompilerID){
    uint32_t awoke = 0;
    
    pthread_mutex_lock(&(PCs[CompilerID].M));

        if(PCs[CompilerID].CompilerState == PCLS_Sleeping){
            awoke = 1;
            PCs[CompilerID].CompilerState = PCLS_Compiling;
            pthread_cond_signal(&(PCs[CompilerID].Cond));
        }
    
    pthread_mutex_unlock(&(PCs[CompilerID].M));
    
    return awoke;
}

void PC_Compile(BMCList* CompilationOrder){
    uint32_t i;
    
    CQ_Enqueue(CompilationOrder);
    
    for(i = 0; i < NumParallelCompilers; i++){
        if(PCs[i].CompilerState == PCLS_Sleeping){
            break;
        }
    }
    
    if(i < NumParallelCompilers){
        if(PC_Awake(i)){
            1;
        }
    }
}
